/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <string.h>
#include <glib.h>
#include "display.h"
#include "prop.h"
#define CREATE_NAME(var, name) (prop_atoms[T_PROP_##var] = XInternAtom((t_display), (name), FALSE))
#define CREATE(var) CREATE_NAME(var, #var)
#define CREATE_(var) CREATE_NAME(var, "_" #var)
Atom prop_atoms[T_PROP_NUM_ATOMS];
gboolean prop_started = FALSE;
void t_prop_startup(void)
{
	if(prop_started)
		return;
	prop_started = TRUE;
	g_assert(t_display);
	CREATE(CARDINAL);
	CREATE(WINDOW);
	CREATE(PIXMAP);
	CREATE(ATOM);
	CREATE(STRING);
	CREATE(COMPOUND_TEXT);
	CREATE(UTF8_STRING);
	CREATE(MANAGER);
	CREATE(WM_COLORMAP_WINDOWS);
	CREATE(WM_PROTOCOLS);
	CREATE(WM_STATE);
	CREATE(WM_DELETE_WINDOW);
	CREATE(WM_TAKE_FOCUS);
	CREATE(WM_NAME);
	CREATE(WM_ICON_NAME);
	CREATE(WM_CLASS);
	CREATE(WM_TRANSIENT_FOR);
	CREATE_(MOTIF_WM_HINTS);
	CREATE_(MOTIF_WM_INFO);
	CREATE_(NET_WM_FULL_PLACEMENT);
	CREATE_(NET_SUPPORTED);
	CREATE_(NET_WM_SYNC_REQUEST);
	CREATE_(NET_WM_SYNC_REQUEST_COUNTER);
	prop_atoms[T_PROP_NET_WM_ORIENTATION_HORZ] = 0;
	prop_atoms[T_PROP_NET_WM_ORIENTATION_VERT] = 1;
	prop_atoms[T_PROP_NET_WM_TOPLEFT] = 0;
	prop_atoms[T_PROP_NET_WM_TOPRIGHT] = 1;
	prop_atoms[T_PROP_NET_WM_BOTTOMRIGHT] = 2;
	prop_atoms[T_PROP_NET_WM_BOTTOMLEFT] = 3;
	CREATE_(LBOXWM_PID);
	CREATE_(LBOXWM_CONFIG_FILE);
	CREATE_(LBOXWM_VERSION);
	CREATE_(LBOXWM_APP_TITLE);
	CREATE_(LBOXWM_APP_NAME);
	CREATE_(LBOXWM_APP_CLASS);
	CREATE_(LBOXWM_APP_TYPE);
}

Atom t_prop_atom(enum wm_prop_atom a)
{
	g_assert(prop_started);
	g_assert(a < T_PROP_NUM_ATOMS);
	return prop_atoms[a];
}

static gboolean get_prealloc(Window win, Atom prop, Atom type, gint size, guchar *data, gulong num)
{
	gboolean ret = FALSE;
	gint res;
	guchar *xdata = NULL;
	Atom ret_type;
	gint ret_size;
	gulong ret_items, bytes_left;
	glong num32 = 32 / size * num;
	res =
		XGetWindowProperty(t_display, win, prop, 0l, num32, FALSE, type, &ret_type, &ret_size, &ret_items, &bytes_left,
		&xdata);
	if(res == Success && ret_items && xdata) {
		if(ret_size == size && ret_items >= num) {
			guint i;
			for(i = 0; i < num; ++i)
				switch (size) {
				case 8:
					data[i] = xdata[i];
					break;
				case 16:
					((guint16 *)data)[i] = ((gushort *)xdata)[i];
					break;
				case 32:
					((guint32 *)data)[i] = ((gulong *)xdata)[i];
					break;
				default:
					g_assert_not_reached();
				}
			ret = TRUE;
		}
		XFree(xdata);
	}
	return ret;
}

static gboolean get_all(Window win, Atom prop, Atom type, gint size, guchar **data, guint *num)
{
	gboolean ret = FALSE;
	gint res;
	guchar *xdata = NULL;
	Atom ret_type;
	gint ret_size;
	gulong ret_items, bytes_left;
	res =
		XGetWindowProperty(t_display, win, prop, 0l, G_MAXLONG, FALSE, type, &ret_type, &ret_size, &ret_items,
		&bytes_left, &xdata);
	if(res == Success) {
		if(ret_size == size && ret_items > 0) {
			guint i;
			*data = g_malloc(ret_items * (size / 8));
			for(i = 0; i < ret_items; ++i)
				switch (size) {
				case 8:
					(*data)[i] = xdata[i];
					break;
				case 16:
					((guint16 *)*data)[i] = ((gushort *)xdata)[i];
					break;
				case 32:
					((guint32 *)*data)[i] = ((gulong *)xdata)[i];
					break;
				default:
					g_assert_not_reached();
				}
			*num = ret_items;
			ret = TRUE;
		}
		XFree(xdata);
	}
	return ret;
}

static gboolean get_text_property(Window win, Atom prop, XTextProperty *tprop, enum wm_prop_text_type type)
{
	if(!(XGetTextProperty(t_display, win, tprop, prop) && tprop->nitems))
		return FALSE;
	if(!type)
		return TRUE;
	switch (type) {
	case T_PROP_TEXT_STRING:
	case T_PROP_TEXT_STRING_XPCS:
	case T_PROP_TEXT_STRING_NO_CC:
		return tprop->encoding == T_PROP_ATOM(STRING);
	case T_PROP_TEXT_COMPOUND_TEXT:
		return tprop->encoding == T_PROP_ATOM(COMPOUND_TEXT);
	case T_PROP_TEXT_UTF8_STRING:
		return tprop->encoding == T_PROP_ATOM(UTF8_STRING);
	default:
		g_assert_not_reached();
	}
}

static void *convert_text_property(XTextProperty *tprop, enum wm_prop_text_type type, gint max)
{
	enum {
		LATIN1,
		UTF8,
		LOCALE
	} encoding;
	const gboolean return_single = (max == 1);
	gboolean ok = FALSE;
	gchar **strlist = NULL;
	gchar *single[1] = { NULL };
	gchar **retlist = single;
	gint i, n_strs;
	if(tprop->encoding == T_PROP_ATOM(COMPOUND_TEXT)) {
		encoding = LOCALE;
		ok = (XmbTextPropertyToTextList(t_display, tprop, &strlist, &n_strs) == Success);
		if(ok) {
			if(max >= 0)
				n_strs = MIN(max, n_strs);
			if(!return_single)
				retlist = g_new0(gchar *, n_strs + 1);
			if(retlist)
				for(i = 0; i < n_strs; ++i)
					retlist[i] = strlist[i];
		}
	} else if(tprop->encoding == T_PROP_ATOM(UTF8_STRING) || tprop->encoding == T_PROP_ATOM(STRING)) {
		gchar *p;
		if(tprop->encoding == T_PROP_ATOM(STRING))
			encoding = LATIN1;
		else
			encoding = UTF8;
		ok = TRUE;
		p = (gchar *)tprop->value;
		n_strs = 0;
		while(p < (gchar *)tprop->value + tprop->nitems) {
			p += strlen(p) + 1;
			++n_strs;
		}
		if(max >= 0)
			n_strs = MIN(max, n_strs);
		if(!return_single)
			retlist = g_new0(gchar *, n_strs + 1);
		if(retlist) {
			p = (gchar *)tprop->value;
			for(i = 0; i < n_strs; ++i) {
				retlist[i] = p;
				p += strlen(p) + 1;
			}
		}
	}
	if(!(ok && retlist)) {
		if(strlist)
			XFreeStringList(strlist);
		return NULL;
	}
	for(i = 0; i < n_strs; ++i) {
		if(encoding == UTF8) {
			const gchar *end;
			g_utf8_validate(retlist[i], -1, &end);
			retlist[i] = g_strndup(retlist[i], end - retlist[i]);
		} else if(encoding == LOCALE) {
			gsize nvalid;
			gchar *utf;
			utf = g_locale_to_utf8(retlist[i], -1, &nvalid, NULL, NULL);
			if(!utf)
				utf = g_locale_to_utf8(retlist[i], nvalid, NULL, NULL, NULL);
			g_assert(utf);
			retlist[i] = utf;
		} else {
			gsize nvalid;
			gchar *utf;
			gchar *p;
			for(p = retlist[i], nvalid = 0; *p; ++p, ++nvalid) {
				register const guchar c = (guchar)*p;
				if((c < 32 && c != 9 && c != 10) || (c >= 127 && c <= 160))
					break;
				if(type == T_PROP_TEXT_STRING_NO_CC && c < 32)
					break;
				if(type == T_PROP_TEXT_STRING_XPCS) {
					const gboolean valid = ((c >= 32 && c < 128) || c == 9 || c == 10);
					if(!valid)
						break;
				}
			}
			utf = g_convert(retlist[i], nvalid, "utf-8", "iso-8859-1", &nvalid, NULL, NULL);
			if(!utf)
				utf = g_convert(retlist[i], nvalid, "utf-8", "iso-8859-1", NULL, NULL, NULL);
			g_assert(utf);
			retlist[i] = utf;
		}
	}
	if(strlist)
		XFreeStringList(strlist);
	if(return_single)
		return retlist[0];
	else
		return retlist;
}

gboolean t_prop_get32(Window win, Atom prop, Atom type, guint32 *ret)
{
	return get_prealloc(win, prop, type, 32, (guchar *)ret, 1);
}

gboolean t_prop_get_array32(Window win, Atom prop, Atom type, guint32 **ret, guint *nret)
{
	return get_all(win, prop, type, 32, (guchar **)ret, nret);
}

gboolean t_prop_get_text(Window win, Atom prop, enum wm_prop_text_type type, gchar **ret_string)
{
	XTextProperty tprop;
	gchar *str;
	gboolean ret = FALSE;
	if(get_text_property(win, prop, &tprop, type)) {
		str = (gchar *)convert_text_property(&tprop, type, 1);
		if(str) {
			*ret_string = str;
			ret = TRUE;
		}
	}
	XFree(tprop.value);
	return ret;
}

gboolean t_prop_get_array_text(Window win, Atom prop, enum wm_prop_text_type type, gchar ***ret_strings)
{
	XTextProperty tprop;
	gchar **strs;
	gboolean ret = FALSE;
	if(get_text_property(win, prop, &tprop, type)) {
		strs = (gchar **)convert_text_property(&tprop, type, -1);
		if(strs) {
			*ret_strings = strs;
			ret = TRUE;
		}
	}
	XFree(tprop.value);
	return ret;
}

void t_prop_set32(Window win, Atom prop, Atom type, gulong val)
{
	XChangeProperty(t_display, win, prop, type, 32, PropModeReplace, (guchar *)&val, 1);
}

void t_prop_set_array32(Window win, Atom prop, Atom type, gulong *val, guint num)
{
	XChangeProperty(t_display, win, prop, type, 32, PropModeReplace, (guchar *)val, num);
}

void t_prop_set_text(Window win, Atom prop, const gchar *val)
{
	XChangeProperty(t_display, win, prop, T_PROP_ATOM(UTF8_STRING), 8, PropModeReplace, (const guchar *)val,
		strlen(val));
}

void t_prop_erase(Window win, Atom prop)
{
	XDeleteProperty(t_display, win, prop);
}

void t_prop_message(gint screen, Window about, Atom messagetype, glong data0, glong data1, glong data2, glong data3,
	glong data4, glong mask)
{
	t_prop_message_to(t_root(screen), about, messagetype, data0, data1, data2, data3, data4, mask);
}

void t_prop_message_to(Window to, Window about, Atom messagetype, glong data0, glong data1, glong data2, glong data3,
	glong data4, glong mask)
{
	XEvent ce;
	ce.xclient.type = ClientMessage;
	ce.xclient.message_type = messagetype;
	ce.xclient.display = t_display;
	ce.xclient.window = about;
	ce.xclient.format = 32;
	ce.xclient.data.l[0] = data0;
	ce.xclient.data.l[1] = data1;
	ce.xclient.data.l[2] = data2;
	ce.xclient.data.l[3] = data3;
	ce.xclient.data.l[4] = data4;
	XSendEvent(t_display, to, FALSE, mask, &ce);
}
